
import java.io.IOException;
import java.util.Timer;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.game.GameCanvas;
import javax.microedition.lcdui.game.LayerManager;
import javax.microedition.lcdui.game.Sprite;
import javax.microedition.lcdui.game.TiledLayer;
import javax.microedition.media.Manager;
import javax.microedition.media.MediaException;
import javax.microedition.media.Player;

/**
 *
 * @author  Gerrit van Roekel, Peter de Bruine, Klaas Dons
 * @version 1.0
 */
public class marioCanvas extends GameCanvas implements Runnable {
    //Var gebruikt voor de muziek op de achtergrond.
    private Main parent;
    Player midiPlayer = null;    //Var gebruikt voor het maken van de sprites en layers.
    Player waveDeathPlayer = null;
    Player waveWinPlayer = null;
    private LayerManager lm;
    private Image sprite_mario;
    private Sprite mario;
    private TiledLayer backGroundLayer;
    private Player waveCoinPlayer = null;
    private TiledLayer backGroundImage;    //Var voor het instellen van de sprite zoals lopen, snelheid e.d.
    public int marioseqDelay = 200;
    private int[] marioSeqWalk = {0, 1, 2};
    private int[] marioSeqJump = {5};
    private int[] marioSeqFall = {6};
    /*De volgende List Intergers, zorgt ervoor dat elke object zijn eigen val, en jump, direction e.d. heeft.
     *      0 lastDirection;
     *      1 lastWalkDirection;
     *      2 jumpSpeed;
     *      3 fallingSpeed; spriteOptions[3]
     *      4 jumpedToMax; spriteOptions[4]
     */
    private float[] marioWalk = {DOWN, RIGHT, 0, 0, 0};
    private int maxJump = 10;  // Jump speed = 3 * 1.04 net zolang totdat deze bij 4 is. Dus na 8 keer is ie bij 4, globaal dan zo'n 26 pixels gesprongen.
    private int walkSpeed = 3;
    private int jumpWalkSpeed = 2;    //Var voor het updaten van de sprites. Volgende frame e.d.
    private SpriteAnimationTask spriteMarioAnimator;
    private Timer timer;    //Var gebruikt tijdens het spel om bijvoorbeeld aante geven welke richting mario loopt.
    private int offsetLayers = 0;
    private Enemy[] enemies = new Enemy[15];
    private QuestionBlock[] newQB = new QuestionBlock[20];
    private Coin coin = new Coin();
    private int iScore = 0;
    private int iWinX = 0; //Als mario voorbij dit punt loopt heeft hij de vlag gehaald, en gewonnen.
        //private Coin newCoin;    //Constante om aan te geven hoe vaak er geupdate moet worden. 
    //Standaard 33 keer per seconde = 33ms
    //Is 30 frames per seconde
    private boolean bLoopGame = true;
    private boolean killLoop = false;
    private boolean bMove = true;
    private static final long UpdateDelay = 33;

    public void setGameLoop(boolean loopGame) {
        this.bLoopGame = loopGame;
    }
    
    //Bij het uitvoeren van deze classe, het display fullscreen zetten, en init aanroepen, om het 
    // spel voor te bereiden.
    public marioCanvas(Main parentMidlet) {
        super(true);
        parent = parentMidlet;
        try {
            this.setFullScreenMode(true);
        } catch (Exception ex) {
            System.out.println(ex.getMessage());
        }
        this.init();
    }

    /**
     * Bereid alles voor, voor het starten van het spel.
     */
    public void init() {
         bMove = true;
        //Maak de spirtes aan, zodat deze op het beeldscherm getoond kunnen worden.
        getSprite_mario();
        getTitleBackground();

        //Start het achtergrond muziekje
        initSound();

        //Maak de layer aan, en zet hierin de verschillende sprites. Dit is onze scene.
        lm = new LayerManager();
        initLayerManager(lm);
        this.lm.setViewWindow(0, 0, this.getWidth(), this.getHeight());

        playMidiBackGround();
        startMusic();
        //Maak een timer aan, en ken hieraan de verschillende sprites die moeten worden geupade.
        this.timer = new Timer();
        this.spriteMarioAnimator = new SpriteAnimationTask(this.mario, true);
        this.timer.scheduleAtFixedRate(this.spriteMarioAnimator, 0, marioseqDelay);
    }

    public void initSound() {
        try {
            waveDeathPlayer = Manager.createPlayer(getClass().getResourceAsStream("/sounds/death.wav"), "audio/x-wav");
            //waveDeathPlayer.prefetch();
        } catch (IOException ex) {
            ex.printStackTrace();
        } catch (MediaException ex) {
            ex.printStackTrace();
        }
        try {
            waveWinPlayer = Manager.createPlayer(getClass().getResourceAsStream("/sounds/winStage.wav"), "audio/x-wav");
            waveWinPlayer.prefetch();
        } catch (IOException ex) {
            ex.printStackTrace();
        } catch (MediaException ex) {
            ex.printStackTrace();
        }
        try {
            waveCoinPlayer = Manager.createPlayer(getClass().getResourceAsStream("/sounds/coin.wav"), "audio/x-wav");
            //waveCoinPlayer.prefetch();
        } catch (IOException ex) {
            ex.printStackTrace();
        } catch (MediaException ex) {
            ex.printStackTrace();
        }
    }

    /**
     * Ken de sprites toe aan de layer die er voor zorgt dat de sprite op het beeldscherm komt
     * @param lm, om welke layer manager gaat het.
     */
    public void initLayerManager(LayerManager lm) {
        this.coin.getCoin().setPosition(5, 5);
        this.coin.getCoin().setVisible(true);
        lm.append(this.coin.getCoin());
        
        this.mario.setPosition(55, 43);
        this.mario.setVisible(true);
        lm.append(this.mario);
        byte temp = RIGHT;
        setSpriteMarioWalk(temp);
        offsetLayers = this.getHeight() - this.backGroundLayer.getHeight();

        //Plaats de coins
        int iCoin = 0;
        int iEnemy = 0;
        for (int row = 0; row < 15; row++) {
          for (int col = 0; col < 224; col++) {
            if (this.backGroundLayer.getCell(col, row) == 7) {
                if (iCoin < this.newQB.length) {
                    newQB[iCoin] = new QuestionBlock();
                    this.newQB[iCoin].getQuestionBlock().setPosition(this.backGroundLayer.getCellWidth() * col, this.backGroundLayer.getCellHeight() * row + offsetLayers);
                    newQB[iCoin].getQuestionBlock().setVisible(true);
                    lm.append(newQB[iCoin].getQuestionBlock());
                    iCoin++;
                }
            } 
            else if (this.backGroundLayer.getCell(col, row) == 8) {
                if (iEnemy < this.enemies.length) {
                   enemies[iEnemy] = new Enemy();
                   this.enemies[iEnemy].enimy.setPosition(this.backGroundLayer.getCellWidth() * col, this.backGroundLayer.getCellHeight() * row + offsetLayers - 20);
                   enemies[iEnemy].enimy.setVisible(true);
                   enemies[iEnemy].enimyWalk[0] = RIGHT;
                   enemies[iEnemy].enimyWalk[1] = LEFT;
                   lm.append(enemies[iEnemy].enimy);
                   iEnemy++;
                }
            }
            else if (this.backGroundLayer.getCell(col, row) == 103) {
               iWinX = this.backGroundLayer.getCellWidth() * col;
            }
          }
        }
                
        this.backGroundLayer.setPosition(0, 0);
        
        this.backGroundLayer.setPosition(0, offsetLayers);
        this.backGroundLayer.setVisible(true);
        lm.append(this.backGroundLayer);
        this.backGroundImage.setPosition(0, offsetLayers);
        this.backGroundImage.setVisible(true);
        lm.append(this.backGroundImage);
    }

    /**
     * Run, hierin begint het spel met lopen, en alles af te handelen. 
     * ook de gamelus is hierin te vinden.
     */
    public void run() {
        Graphics g = getGraphics();
        //Game Loop
        while (!killLoop) {
            if (this.bLoopGame == true) {
            //Haal de huidige tijd op
            long time = System.currentTimeMillis();

            g.setColor(0x005C94FC); //Zet de achtergrond kleur van het spel, en tekendaarna een rechthoek met deze kleur

            g.fillRect(0, 0, this.getWidth(), this.getHeight());
            int keyState = getKeyStates();
            if ((keyState & LEFT_PRESSED) != 0 && bMove) {
                if (this.marioWalk[1] != LEFT) {
                    //this.lastDirection = LEFT;
                    this.marioWalk[1] = LEFT;
                    setSpriteMarioWalk((byte) this.marioWalk[1]);
                    this.mario.move(-9, 0);
                }
                this.spriteMarioAnimator.forward();
                if (this.marioWalk[2] == 0 && this.marioWalk[3] == 0) {
                    this.mario.move(walkSpeed * -1, 0);
                } else {
                    this.mario.move(jumpWalkSpeed * -1, 0);
                }
                if ((keyState & UP_PRESSED) != 0) {
                    setToJump();
                } else {
                    this.marioWalk[0] = DOWN;
                }
            } else if ((keyState & RIGHT_PRESSED) != 0 && bMove) {
                if (this.marioWalk[1] != RIGHT) {
                    //this.lastDirection = RIGHT;
                    this.marioWalk[1] = RIGHT;
                    setSpriteMarioWalk((byte) this.marioWalk[1]);
                    this.mario.move(9, 0);
                }
                this.spriteMarioAnimator.forward();
                if (this.marioWalk[2] == 0 && this.marioWalk[3] == 0) {
                    this.mario.move(walkSpeed, 0);
                } else {
                    this.mario.move(jumpWalkSpeed, 0);
                }
                if ((keyState & UP_PRESSED) != 0) {
                    setToJump();
                } else {
                    this.marioWalk[0] = DOWN;
                }
            } else if ((keyState & UP_PRESSED) != 0 && bMove) {
                setToJump();
            } else {
                this.spriteMarioAnimator.setMoving(false);
                this.marioWalk[0] = DOWN;
            }
            if (this.bMove == true) {
                    this.marioWalk = spriteGrafity(this.mario, this.marioWalk);
            } else {
                if (this.mario.getX() <= this.iWinX + 95) {
                    this.marioWalk[1] = RIGHT;
                    this.mario.move(1, 0);
                    this.spriteMarioAnimator.forward();
                } else {
                    this.spriteMarioAnimator.setMoving(false);
                    this.marioWalk[0] = DOWN;
                    setSpriteMarioJump();
                    if (waveWinPlayer.getState() != waveWinPlayer.STARTED && waveDeathPlayer.getState() != waveDeathPlayer.STARTED) {
                        this.exitGame(iScore);
                    }
                }
            }

            UpdateQuestionBlocks();
            
            moveEnimies();
            enemyVSmario();
            
            updateView();
            this.lm.paint(g, 0, 0);
            flushGraphics(0, 0, this.getWidth(), this.getHeight());

            try {
                //Bepaal hoe lang het uitvoeren geduurt heeft
                time = System.currentTimeMillis() - time;
                if (time < UpdateDelay) {
                    Thread.sleep(UpdateDelay - time);
                }
            } catch (InterruptedException ex) {
            }
            }
        }
    }

    public void moveEnimies() {
        for (int i = 0; i < this.enemies.length; i++) {
            if (enemies[i] != null) {
              enemies[i].enimyWalk = spriteGrafity(enemies[i].enimy, enemies[i].enimyWalk);
              enemies[i].moveEnemy();
            }
        }
    }

    public void setToJump() {
        this.mario.move(0, 1);
        if (this.marioWalk[0] != UP && this.marioWalk[4] == 0 && this.mario.collidesWith(this.backGroundLayer, true)) {
            this.marioWalk[0] = UP;
            this.marioWalk[2] = this.maxJump;
            this.marioWalk[3] = 0;
            this.setSpriteMarioJump();
        //this.playJump();
        }
        this.mario.move(0, -1);
        this.spriteMarioAnimator.forward();
    }

    /**
     * Controleer waar Mario zich bevind, en stel hiervoor een zwaarte kracht in,
     * zodat Mario weer naar beneden valt.
     * @param spriteOptions, is een lijst van 5 waardes:
     *      0 lastDirection;
     *      1 lastWalkDirection;
     *      2 jumpSpeed;
     *      3 fallingSpeed; spriteOptions[3]
     *      4 jumpedToMax; spriteOptions[4]
     */
    public float[] spriteGrafity(Sprite spriteCheck, float[] spriteOptions) {
        //Check of niet aan een zij kant zit, anders is er een botsing, en zal de val niet gedecteteerd worden, Dus eerst zeker van zijn
        //dat er geen spraken is van een zijkants botsing.
        //Zet Mario zo, dat deze geen botsing maakt met de omgeving sprites.
        //Botsing voor LEFT
            boolean changed = false;
            spriteCheck.move(0, 5);
            if (spriteCheck.collidesWith(this.backGroundLayer, true)) {
                boolean bCheckCol = false;
                spriteCheck.move(0, -5);
                spriteCheck.move(-2, -4);
                //if ((collidesWithDirection(spriteCheck, this.backGroundLayer) & DOWN) != 0) {
                while ((spriteCheck.collidesWith(this.backGroundLayer, true) || spriteCheck.getX() < 0) && spriteOptions[1] == LEFT) {
                    spriteCheck.move(1, 0);
                    bCheckCol = true;
                }
                //}
                if (spriteOptions[1] == LEFT && bCheckCol) {
                    spriteCheck.move(1, 0);
                    if (!spriteCheck.equals(this.mario)) {
                        spriteOptions[1] = RIGHT;
                        changed = true;
                    }
                }
                spriteCheck.move(2, 0);
                //Botsing voor Rechts
                // if ((collidesWithDirection(spriteCheck, this.backGroundLayer) & DOWN) != 0) {          
                while ((spriteCheck.collidesWith(this.backGroundLayer, true) || (spriteCheck.getX() + spriteCheck.getWidth()) > this.backGroundLayer.getWidth()) && spriteOptions[1] == RIGHT) {
                    spriteCheck.move(-1, 0);
                    bCheckCol = true;
                }
                // }
                if (spriteOptions[1] == RIGHT && bCheckCol) {
                    spriteCheck.move(-1, 0);
                    if (!spriteCheck.equals(this.mario) && changed == false) {
                        spriteOptions[1] = LEFT;
                    }
                }
                spriteCheck.move(0, 3);
            } else {
                spriteCheck.move(0, -5);
            }
        
        if (spriteCheck.collidesWith(this.backGroundLayer, true) == false && (spriteOptions[0] != UP || spriteOptions[4] == 1)) {
            spriteOptions[3] *= 1.04;
            spriteOptions[0] = DOWN;
            if (spriteOptions[3] == 0) {
                spriteOptions[3] = 3;
                if (spriteCheck.equals(this.mario)) {
                    spriteCheck.setFrameSequence(marioSeqFall);
                }
            }
            spriteCheck.move(0, (int) spriteOptions[3]);
            // Nog niet zeker of dit de goede oplossing is.
            //Zorg ervoor, dat als deze toch halverwegen een blok valt, er net zolang
            //eentje omhoog wordt gegaan, totdat Mario goed staat.
            if (spriteCheck.collidesWith(this.backGroundLayer, true)) {
                while (spriteCheck.collidesWith(this.backGroundLayer, true)) {
                    spriteCheck.move(0, -1);
                }
                //En vervoglens, eentje omhoog, zodat de 'botsing' wordt opgelost
                spriteCheck.move(0, 1);
                if (spriteCheck.equals(this.mario)) {
                    setSpriteMarioWalk((byte) spriteOptions[1]);
                }
            }
        } else if (spriteOptions[0] == UP && spriteOptions[4] == 0) {
            if (spriteOptions[2] > 1) {
                spriteOptions[2] /= 1.08;
                if (spriteOptions[2] < 1) {
                    spriteOptions[2] = 0;
                    spriteOptions[4] = 1;
                }
                spriteCheck.move(0, (int) (spriteOptions[2] * -1));
            }
        } else {
            spriteOptions[2] = 0;
            spriteOptions[3] = 0;
            spriteOptions[0] = DOWN;
            spriteOptions[4] = 0;
        }


        //Botsing voor UP, dan maxJump aan zetten, eerst moet mario weer vallen, dan mag ie weer springen.
        while (spriteCheck.collidesWith(this.backGroundLayer, true) && spriteOptions[0] == UP) {
            spriteOptions[4] = 1;
            spriteCheck.move(0, 1);
        }
        spriteCheck.move(0, 1);

        //GAME OVER, Mario loopt uit het scherm
        if (spriteCheck.getY() > this.backGroundLayer.getHeight() + offsetLayers) {
            spriteCheck.setPosition(30, 30);
            spriteOptions[3] = 0;
            if (spriteCheck.equals(this.mario)) {
                gameover();
            }
        }
        //Als mario bij de vlag komt, dan heeft hij gewonnen.
        if (spriteCheck.equals(this.mario)) {
            if (spriteCheck.getX() >= iWinX - 16 && bMove) {
                this.mario.setPosition(this.iWinX + 16, this.offsetLayers + 180); // + 95
                this.marioWalk[0] = DOWN;
                this.marioWalk[1] = RIGHT;
                setSpriteMarioWalk((byte) this.marioWalk[1]);
                this.bMove = false;
                this.gameWon();
            }
        }

        return spriteOptions;
    }

    public void enemyVSmario() {
        boolean collision = false;
        for (int i = 0; i < this.enemies.length; i++) {
            if (enemies[i] != null) {
                if (this.mario.collidesWith(enemies[i].enimy, true)) {
                    collision = true;
                }
            }
        }
        if (collision == true) {
            gameover();
        }
    }

    public void gameover() {
        playGameOver();
        this.bMove = false;
        this.mario.setPosition(iWinX + 250, -40);
    }
    
    public void gameWon() {
        playWin();
    }
    
    public void exitGame(int score) {
        this.killLoop = true;
        System.out.println("Exit game: ");
        System.out.println(score);
        this.closeMusic();
        //parent.killApp();
     }

    /**
     * De camera loopt op mario, dus bij alles Mario volgen met wat deze doet.
     * Enkel de X hoeft aangepast te worden.
     */
    public void updateView() {
        int x = 0;
        int y = 0;

        x = this.mario.getX() - (this.getWidth() / 4);
        if (x < 0) {
            x = 0;
        }
        if ((x + this.getWidth()) > this.backGroundLayer.getWidth()) {
            x = this.backGroundLayer.getWidth() - this.getWidth();
        }
        updateHighScoreView(x);
        this.lm.setViewWindow(x, y, this.getWidth(), this.getHeight());
    }

    public void updateHighScoreView(int x) {
        this.coin.getCoin().setPosition(x + 5, 5);
        
    }
    
    /**
     * Stop of start met het spelen van het muziekje.
     */
    public void stopMusic() {
        try {
            midiPlayer.stop();
        } catch (MediaException ex) {
        }
    }

    public void startMusic() {
        try {
            midiPlayer.start();
        } catch (MediaException ex) {
        }
    }

    public void closeMusic() {
        if (midiPlayer != null) {
            midiPlayer.close();
        }
        if (waveDeathPlayer != null) {
            waveDeathPlayer.close();
        }
        if (waveWinPlayer != null) {
            waveWinPlayer.close();
        }
        if (waveCoinPlayer != null) {
            waveCoinPlayer.close();
        }
    }

    /**
     * Zorg ervoor dat Mario eruit ziet alsof hij aan het lopen is.
     * @param Direction geef aan welke kant Mario aan het oplopen is.
     */
    public void setSpriteMarioWalk(byte Direction) {
        if (Direction == RIGHT) {
            this.mario.setTransform(Sprite.TRANS_MIRROR);
        } else {
            this.mario.setTransform(Sprite.TRANS_NONE);
        }
        this.mario.setFrameSequence(marioSeqWalk);
    }

    /** 
     * Zorg ervoor dat mario eruit ziet alsof hij springt.
     */
    public void setSpriteMarioJump() {
        this.mario.setFrameSequence(marioSeqJump);
    }

    /**
     * Zorg ervoor dat op de achtergrond een muziek je kan worden afgespeeld. Roep hiervoor de 
     * muziek manager aan, en maak hier op een Player, die vervolgens de streams afspeelt.
     */
    public void playMidiBackGround() {
        try {
            midiPlayer = Manager.createPlayer(getClass().getResourceAsStream("/sounds/marioTheme.mid"), "audio/midi");
            if (midiPlayer != null) {
                midiPlayer.setLoopCount(-1);
                midiPlayer.start();
            }
        } catch (Exception ex) {
        }
    }

    public void playGameOver() {
        this.stopMusic();
        if (waveDeathPlayer != null) {
            try {
                if (waveDeathPlayer.getState() != waveDeathPlayer.STARTED) {
                    waveDeathPlayer.setLoopCount(1);
                }
                waveDeathPlayer.start();
            } catch (MediaException ex) {
                ex.printStackTrace();
            }
        }
    }

    public void playWin() {
        this.stopMusic();
        if (waveWinPlayer != null) {
            try {
                if (waveWinPlayer.getState() != waveWinPlayer.STARTED) {
                    waveWinPlayer.setLoopCount(1);
                }
                waveWinPlayer.start();
            } catch (MediaException ex) {
                ex.printStackTrace();
            }
        }
    }
    
    public void playCoin() {
        if (waveCoinPlayer != null) {
            try {
                if (waveCoinPlayer.getState() != waveCoinPlayer.STARTED) {
                    waveCoinPlayer.setLoopCount(1);
                }
                waveCoinPlayer.start();
            } catch (MediaException ex) {
                ex.printStackTrace();
            }
        }
    }

    private void UpdateQuestionBlocks() {
        //Check for collision with questionblock
        for (int i = 0; i < this.newQB.length; i++) {
            if (this.newQB[i] != null) {
                if (!this.newQB[i].isHit()) {
                    if (this.newQB[i].CheckForCollision(this.mario)) {
                        //Add the coin to the view
                        this.lm.append(this.newQB[i].getHitCoin().getCoin());
                        iScore++;
                        playCoin();
                    }
                } else if (!this.newQB[i].isHitAnimationReady()) {
                    //Update the animation
                    this.newQB[i].UpdateCoinAnimation();
                } else {
                    //Remove from view
                    this.lm.remove(this.newQB[i].getHitCoin().getCoin());
                    this.lm.remove(this.newQB[i].getQuestionBlock());
                }
            }
        }
    }

    /**
     * Maak de mario sprite aan. Van het plaatje 16 breedt, bij 27 hoog.
     * Zet de eerste mario sequence als een mario die aan het lopen is.
     */
    public void getSprite_mario() {
        try {
            if (this.sprite_mario == null) {
                this.sprite_mario = Image.createImage("/sprites/sprite_mario.png");
            }
            this.mario = new Sprite(this.sprite_mario, 16, 27);
            this.mario.setFrameSequence(marioSeqWalk);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    /**
     * Maak het spel level aan.
     */
    public void getTitleBackground() {
        LoadGraphics loadSprites = new LoadGraphics();
        this.backGroundLayer = loadSprites.getForeGround();
        this.backGroundImage = loadSprites.getBackground();
    }
}
